package com.acc.sys.framework;

import apijson.*;
import apijson.column.ColumnUtil;
import apijson.framework.APIJSONConstant;
import apijson.framework.APIJSONCreator;
import apijson.framework.APIJSONSQLConfig;
import apijson.framework.APIJSONVerifier;
import apijson.orm.*;
import apijson.orm.JSONRequest;
import apijson.orm.exception.NotLoggedInException;
import apijson.orm.exception.OutOfRangeException;
import apijson.orm.exception.UnsupportedDataTypeException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.acc.utils.DbUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.rmi.ServerException;
import java.util.*;

import static apijson.RequestMethod.GET;
import static apijson.framework.APIJSONConstant.ACCESS_;
import static apijson.framework.APIJSONConstant.REQUEST_;

public class AccVerifier extends APIJSONVerifier<Long> {
    public static boolean ENABLE_VERIFY_OWNER_ROLE = true;

    @NotNull
    public static Map<String, String> ACCESS_OWNER_MAP = new LinkedHashMap<>();

    public AccVerifier() {
    }

    @Override
    public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception {
        if (StpUtil.isLogin()) {
            Object superAdmin = StpUtil.getExtra("super_admin");
            if (superAdmin != null && StrUtil.equals("1", superAdmin.toString())) {
                role = ADMIN;
            } else if (ACCESS_OWNER_MAP.get(table) != null) {
                role = OWNER;
            } else {
                role = LOGIN;
            }
        } else {
            role = UNKNOWN;
        }
        verifyAllowRole(config, table, method, role); //验证允许的角色
        verifyUseRole(config, table, method, role); //验证使用的角色
    }

    /**
     * 校验请求使用的角色，角色不好判断，让访问者发过来角色名，OWNER,CONTACT,ADMIN等
     *
     * @param config
     * @param table
     * @param method
     * @param role
     * @return
     * @throws Exception
     * @see {@link apijson.JSONObject#KEY_ROLE}
     */
    public void verifyUseRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception {
        Log.d(TAG, "verifyUseRole  table = " + table + "; method = " + method + "; role = " + role);
        //验证角色，假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

        String visitorIdKey = getVisitorIdKey(config);
        if (table == null) {
            table = config == null ? null : config.getTable();
        }
        if (method == null) {
            method = config == null ? GET : config.getMethod();
        }
        if (role == null) {
            role = config == null ? UNKNOWN : config.getRole();
        }

        Object requestId;
        switch (role) {
            case LOGIN://verifyRole通过就行
                break;
            case CONTACT:
            case CIRCLE:
                // TODO 做一个缓存contactMap<visitorId, contactArray>，提高[]:{}查询性能， removeAccessInfo时map.remove(visitorId)
                // 不能在 Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件！
                List<Object> list = visitor.getContactIdList() == null
                        ? new ArrayList<Object>() : new ArrayList<Object>(visitor.getContactIdList());
                if (CIRCLE.equals(role)) {
                    list.add(visitorId);
                }

                // key!{}:[] 或 其它没有明确id的条件 等 可以和 key{}:[] 组合。类型错误就报错
                requestId = config.getWhere(visitorIdKey, true); // JSON 里数值不能保证是 Long，可能是 Integer
                @SuppressWarnings("unchecked")
                Collection<Object> requestIdArray = (Collection<Object>) config.getWhere(visitorIdKey + "{}", true); // 不能是 &{}， |{} 不要传，直接 {}
                if (requestId != null) {
                    if (requestIdArray == null) {
                        requestIdArray = new JSONArray();
                    }
                    requestIdArray.add(requestId);
                }

                if (requestIdArray == null) { // 可能是 @ 得到 || requestIdArray.isEmpty()) { // 请求未声明 key:id 或 key{}:[...] 条件，自动补全
                    config.putWhere(visitorIdKey + "{}", JSON.parseArray(list), true); // key{}:[] 有效，SQLConfig 里 throw NotExistException
                } else { // 请求已声明 key:id 或 key{}:[] 条件，直接验证
                    for (Object id : requestIdArray) {
                        if (id == null) {
                            continue;
                        }

                        if (id instanceof Number) { // 不能准确地判断 Long，可能是 Integer
                            if (((Number) id).longValue() <= 0 || list.contains(Long.valueOf("" + id)) == false) { // Integer等转为 Long 才能正确判断，强转崩溃
                                throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table
                                        + " 不允许 " + role + " 用户的 " + method.name() + " 请求！");
                            }
                        } else if (id instanceof String) {
                            if (StringUtil.isEmpty(id) || list.contains(id) == false) {
                                throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table
                                        + " 不允许 " + role + " 用户的 " + method.name() + " 请求！");
                            }
                        } else {
                            throw new UnsupportedDataTypeException(table + ".id 类型错误，类型必须是 Long/String！");
                        }
                    }
                }
                break;
            case OWNER:
                verifyOwner(config, table);
                break;
            case ADMIN://这里不好做，在特定接口内部判。 可以是  /get/admin + 固定秘钥  Parser#needVerify，之后全局跳过验证
                verifyAdmin();
                break;
            default://unknown，verifyRole通过就行
                break;
        }
    }

    public void verifyOwner(SQLConfig<Long> config, String table) throws IllegalAccessException {
        Object visitorId = StpUtil.getLoginId();
        String visitorIdKey = ACCESS_OWNER_MAP.get(table);
        if (config.getMethod() == RequestMethod.POST) {
            List<String> c = config.getColumn();
            List<List<Object>> ovs = config.getValues();
            if ((c == null || c.isEmpty()) || (ovs == null || ovs.isEmpty())) {
                throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value ！");
            }

            int index = c.indexOf(visitorIdKey);
            if (index >= 0) {
                Object oid;
                for (List<Object> ovl : ovs) {
                    oid = ovl == null || index >= ovl.size() ? null : ovl.get(index);
                    if (oid == null || !StringUtil.getString(oid).equals("" + visitorId)) {
                        throw new IllegalAccessException("");
                    }
                }
            } else {
                List<String> nc = new ArrayList<>(c);
                nc.add(visitorIdKey);
                config.setColumn(nc);

                List<List<Object>> nvs = new ArrayList<>();
                List<Object> nvl;
                for (List<Object> ovl : ovs) {
                    nvl = ovl == null || ovl.isEmpty() ? new ArrayList<>() : new ArrayList<>(ovl);
                    nvl.add(visitorId);
                    nvs.add(nvl);
                }
                config.setValues(nvs);
            }
        } else {
            config.putWhere(visitorIdKey, visitorId, true);
        }
    }


    public void verifyLogin() throws NotLoggedInException {
        if (StpUtil.isLogin()) {
            throw new NotLoggedInException("未登录或登录过期，请登录后再操作！");
        }
    }

    @Override
    public void verifyAdmin() throws OutOfRangeException {
        Object superAdmin = StpUtil.getExtra("super_admin");
        if (superAdmin == null || !StrUtil.equals("1", superAdmin.toString())) {
            throw new OutOfRangeException("权限不足");
        }
    }

    /**
     * 初始化，加载所有权限配置
     *
     * @param shutdownWhenServerError
     * @param creator
     * @param table                   表内自定义数据过滤条件
     * @return
     * @throws ServerException
     */
    @SuppressWarnings("unchecked")
    public static <T> JSONObject initAccess(boolean shutdownWhenServerError, APIJSONCreator<T> creator, JSONObject table) throws ServerException {
        if (creator == null) {
            creator = (APIJSONCreator<T>) APIJSON_CREATOR;
        }
        APIJSON_CREATOR = creator;


        boolean isAll = table == null || table.isEmpty();

        JSONObject access = isAll ? new JSONRequest() : table;
        if (!Log.DEBUG) {
            access.put(APIJSONConstant.KEY_DEBUG, 0);
        }
        JSONRequest accessItem = new JSONRequest();
        accessItem.put(ACCESS_, access);

        JSONRequest request = new JSONRequest();
        request.putAll(accessItem.toArray(0, 0, ACCESS_));


        JSONObject response = creator.createParser().setMethod(RequestMethod.GET).setNeedVerify(false).parseResponse(request);
        if (!JSONResponse.isSuccess(response)) {
            Log.e(TAG, "\n\n\n\n\n !!!! 查询权限配置异常 !!!\n" + response.getString(JSONResponse.KEY_MSG) + "\n\n\n\n\n");
            onServerError("查询权限配置异常 !", shutdownWhenServerError);
        }

        JSONArray list = response.getJSONArray(ACCESS_ + "[]");
        int size = list == null ? 0 : list.size();
        if (isAll && size <= 0) {
            Log.w(TAG, "initAccess isAll && size <= 0，，没有可用的权限配置");
            return response;
        }

        Log.d(TAG, "initAccess < for ACCESS_MAP.size() = " + ACCESS_MAP.size() + " <<<<<<<<<<<<<<<<<<<<<<<<");

        Map<String, Map<RequestMethod, String[]>> newMap = new LinkedHashMap<>();
        Map<String, String> newOwnerMap = new LinkedHashMap<>();
        Map<String, Map<String, Object>> fakeDeleteMap = new LinkedHashMap<>();
        Map<String, String> newTKMap = new LinkedHashMap<>();

        SortedMap<Integer, Map<String, List<String>>> versionedTableColumnMap = new TreeMap<>(ColumnUtil.DESC_COMPARATOR);
        SortedMap<Integer, Map<String, Map<String, String>>> versionedKeyColumnMap = new TreeMap<>(ColumnUtil.DESC_COMPARATOR);
        for (int i = 0; i < size; i++) {
            JSONObject item = list.getJSONObject(i);
            if (item == null) {
                continue;
            }

            Map<RequestMethod, String[]> map = new HashMap<>();
            map.put(RequestMethod.GET, JSON.parseObject(item.getString("get"), String[].class));
            map.put(RequestMethod.HEAD, JSON.parseObject(item.getString("head"), String[].class));
            map.put(RequestMethod.GETS, JSON.parseObject(item.getString("gets"), String[].class));
            map.put(RequestMethod.HEADS, JSON.parseObject(item.getString("heads"), String[].class));
            map.put(RequestMethod.POST, JSON.parseObject(item.getString("post"), String[].class));
            map.put(RequestMethod.PUT, JSON.parseObject(item.getString("put"), String[].class));
            map.put(RequestMethod.DELETE, JSON.parseObject(item.getString("delete"), String[].class));

            String name = item.getString("name");
            String alias = item.getString("alias");

            Map<String, Object> fakemap = new HashMap<>();
            String deletedKey = item.getString(AbstractSQLConfig.KEY_DELETED_KEY);
            if (StringUtil.isNotEmpty(deletedKey, true)) {
                boolean containNotDeletedValue = item.containsKey(AbstractSQLConfig.KEY_NOT_DELETED_VALUE);
                Object deletedValue = item.getString(AbstractSQLConfig.KEY_DELETED_VALUE);
                if (!containNotDeletedValue && StringUtil.isEmpty(deletedValue, true)) {
                    onServerError(
                            "Access表 id = " + item.getString("id") + " 对应的 "
                                    + AbstractSQLConfig.KEY_DELETED_VALUE + " 的值不能为空！或者必须包含字段 "
                                    + AbstractSQLConfig.KEY_NOT_DELETED_VALUE + " ！"
                            , shutdownWhenServerError
                    );
                }
                fakemap.put(AbstractSQLConfig.KEY_DELETED_KEY, deletedKey);
                fakemap.put(AbstractSQLConfig.KEY_DELETED_VALUE, deletedValue);
                if (containNotDeletedValue) {
                    fakemap.put(AbstractSQLConfig.KEY_NOT_DELETED_VALUE, item.get(AbstractSQLConfig.KEY_NOT_DELETED_VALUE));
                }
            }

            /**TODO
             * 以下判断写得比较复杂，因为表设计不够好，但为了兼容旧版 APIJSON 服务 和 APIAuto 工具而保留了下来。
             * 如果是 name 为接口传参的 表对象 的 key，对应一个可缺省的 tableName，判断就会简单不少。
             */

            if (StringUtil.isEmpty(name, true)) {
                onServerError("字段 name 的值不能为空！", shutdownWhenServerError);
            }

            if (StringUtil.isEmpty(alias, true)) {
                if (!JSONRequest.isTableKey(name)) {
                    onServerError("name: " + name + "不合法！字段 alias 的值为空时，name 必须为合法表名！", shutdownWhenServerError);
                }

                alias = name;
            } else if (!JSONRequest.isTableKey(alias)) {
                onServerError("alias: " + alias + "不合法！字段 alias 的值只能为 空 或者 合法表名！", shutdownWhenServerError);
            }

            newMap.put(alias, map);
            newOwnerMap.put(alias, item.getString("owner"));
            fakeDeleteMap.put(alias, fakemap);
            newTKMap.put(alias, name);

            if (ENABLE_VERIFY_COLUMN) {
                JSONObject columns = item.getJSONObject("columns");
                Set<Map.Entry<String, Object>> set = columns == null ? null : columns.entrySet();
                if (set != null) {

                    for (Map.Entry<String, Object> entry : set) {
                        Integer version = entry == null ? null : Integer.valueOf(entry.getKey()); // null is not possible
                        Object val = version == null ? null : entry.getValue();
                        if (val == null) {
                            continue;
                        }

                        Map<String, Map<String, String>> kcm = new LinkedHashMap<>(); // versionedKeyColumnMap.get(version);
                        Map<String, String> cm = new LinkedHashMap<>(); // kcm.get(alias);

                        String[] cs = StringUtil.split(String.valueOf(val));
                        List<String> l = new ArrayList<>();
                        for (int j = 0; j < cs.length; j++) {
                            String s = cs[j];
                            Entry<String, String> ety = Pair.parseEntry(s, true);
                            String k = ety.getKey();
                            String v = ety.getValue();
                            if (!StringUtil.isName(k) || (v != null && !StringUtil.isName(v))) {
                                throw new IllegalArgumentException("后端 Access 表中 name: " + name + " 对应 columns 字段的值 "
                                        + version + ":value 中第 " + j + " 个字段 column:alias 中字符 " + s + " 不合法！"
                                        + "alias 可缺省，但 column, alias 都必须为合法的变量名！"
                                        + " ！ ！ety == null || StringUtil.isName(ety.getKey()) == false "
                                        + " || (ety.getValue() != null && StringUtil.isName(ety.getValue()) == false)");
                            }

                            l.add(k);
                            cm.put(v == null ? k : v, k);
                        }

                        Map<String, List<String>> m = new LinkedHashMap<>();
                        m.put(alias, l);
                        versionedTableColumnMap.put(version, m);

                        kcm.put(alias, cm);
                        versionedKeyColumnMap.put(version, kcm);
                    }
                }
            }
        }

        if (isAll) {  // 全量更新
            ACCESS_MAP = newMap;
            ACCESS_OWNER_MAP = newOwnerMap;
            ACCESS_FAKE_DELETE_MAP = fakeDeleteMap;
            APIJSONSQLConfig.TABLE_KEY_MAP = newTKMap;
        } else {
            ACCESS_MAP.putAll(newMap);
            ACCESS_OWNER_MAP.putAll(newOwnerMap);
            ACCESS_FAKE_DELETE_MAP.putAll(fakeDeleteMap);
            APIJSONSQLConfig.TABLE_KEY_MAP.putAll(newTKMap);
        }

        if (ENABLE_VERIFY_COLUMN) {
            if (isAll) { // 全量更新
                ColumnUtil.VERSIONED_TABLE_COLUMN_MAP = versionedTableColumnMap;
                ColumnUtil.VERSIONED_KEY_COLUMN_MAP = versionedKeyColumnMap;
            } else {
                ColumnUtil.VERSIONED_TABLE_COLUMN_MAP.putAll(versionedTableColumnMap);
                ColumnUtil.VERSIONED_KEY_COLUMN_MAP.putAll(versionedKeyColumnMap);
            }
            ColumnUtil.init();
        }

        Log.d(TAG, "initAccess  for /> ACCESS_MAP.size() = " + ACCESS_MAP.size() + " >>>>>>>>>>>>>>>>>>>>>>>");
        return response;
    }

}
